// 
//  Copyright (C) 2009 Jacek Trubłajewicz
// 
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//  
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 

using System;
using System.Collections.Generic;
using System.Threading;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Xml;
using System.Text.RegularExpressions;
using System.Text;
using Mono.Unix;
using NLog;

namespace wxGadu
{	
	/// <summary>
	/// Manages chat history
	/// </summary>
	public class HistoryManager
	{
		/// <summary>
		/// Collection of chats currently active or 
		/// loaded into history manager
		/// </summary>
		private Dictionary<int, List<HistoryMsg>> chats;
		private Mutex chatsMutex;
		private Dispatcher dispatcher;
		private FileStream fs;
		private MessageFormatter inFormatter;
		private MessageFormatter outFormatter;
#if LOG
		private Logger logger = LogManager.GetCurrentClassLogger();
#endif
		
		public HistoryManager(Dispatcher dispatch)
		{
			dispatcher = dispatch;
			
			chatsMutex = new Mutex();
			chats = new Dictionary<int, List<HistoryMsg>>();
			
			inFormatter = new MessageFormatter(FormatterPaterns.INCOMING_PATTERN);
			outFormatter = new MessageFormatter(FormatterPaterns.OUTGOING_PATTERN);
		}
		
		/// <summary>
		/// Adds single chat message to history
		/// </summary>
		/// <param name="callerUin">
		/// A <see cref="System.Int32"/>
		/// caller UIN
		/// </param>
		/// <param name="msg">
		/// A <see cref="ChatMessage"/>
		/// chat message
		/// </param>
		public void AddChatMessage(int callerUin, ChatMessage msg)
		{
			chatsMutex.WaitOne();
			{
				if (!chats.ContainsKey(callerUin))
					chats.Add(callerUin, new List<HistoryMsg>());
				
				HistoryMsg message = new HistoryMsg(msg.Uin, msg.Name, msg.Time, msg.Message,
				                                                    callerUin == msg.Uin ? ChatMessageType.IN : ChatMessageType.OUT);
				chats[callerUin].Add(message);
			}
			chatsMutex.ReleaseMutex();
		}
		
		/// <summary>
		/// Saves chat to history file
		/// </summary>
		/// <param name="callerUin">
		/// A <see cref="System.Int32"/>
		/// caller UIN
		/// </param>
		/// <remarks>
		/// Content of <c>chats</c> collection for 
		/// <c>callerUin</c> will be deleted after
		/// write
		/// </remarks>
		public void DumpChatToFile(int callerUin)
		{
			chatsMutex.WaitOne();
			{
				if (!chats.ContainsKey(callerUin))
				{
#if LOG
					logger.Warn(String.Format(Catalog.GetString("History manager doesn't contain history UIN: {0}"), callerUin));
#endif		
					chatsMutex.ReleaseMutex();
					return;
				}
				
				if (chats[callerUin].Count < 1)
				{
					chatsMutex.ReleaseMutex();
					return;
				}
				
				string contactHistoryDir = dispatcher.AppCfg.HistoryDir + Path.DirectorySeparatorChar + callerUin.ToString();
				
				// check if history dir exists
				if (!Directory.Exists(dispatcher.AppCfg.HistoryDir))
				{
					try
					{
#if LOG
						logger.Trace(String.Format(Catalog.GetString("History directory: {0} don't exist, creating..."), dispatcher.AppCfg.HistoryDir));
#endif			
						Directory.CreateDirectory(dispatcher.AppCfg.HistoryDir);
					}
					catch (Exception e)
					{
#if LOG
						logger.ErrorException(String.Format(Catalog.GetString("Unable to create history directory: {0}"), dispatcher.AppCfg.HistoryDir), e);
#endif				
						dispatcher.MainWindow.HistoryErrorMessageDialog(
						                                                String.Format(
						                                                              Catalog.GetString("Unable to create history directory: {0}\n{1}"), dispatcher.AppCfg.HistoryDir, e));
						chatsMutex.ReleaseMutex();
						return;
					}
				}
				
				// check if caller history subdir exists				
				if (!Directory.Exists(contactHistoryDir))
				{
					try
					{
#if LOG
						logger.Trace(String.Format(Catalog.GetString("Contact history directory: {0} don't exist, creating..."), contactHistoryDir));
#endif			
						Directory.CreateDirectory(contactHistoryDir);
					}
					catch (Exception e)
					{
#if LOG
						logger.ErrorException(String.Format(Catalog.GetString("Unable to create contact history directory: {0}"), contactHistoryDir), e);
#endif						
						dispatcher.MainWindow.HistoryErrorMessageDialog(
						                                                String.Format(
						                                                              Catalog.GetString("Unable to create contact history directory: {0}\n{1}"), contactHistoryDir, e));
						chatsMutex.ReleaseMutex();
						return;
					}
				}
				
				// check did we create history file today and file exists
				DateTime now = DateTime.Now;
				string historyFileName = contactHistoryDir + Path.DirectorySeparatorChar + now.ToShortDateString() + ".xml";
				
				if (!File.Exists(historyFileName))
				{
					SerializeChatHistory(callerUin, historyFileName);
						
					chatsMutex.ReleaseMutex();
					return;
				}
				else // if file todays history file exists 
				{
#if LOG
					logger.Trace(String.Format(Catalog.GetString("UIN: {0} history file exists, loading and appending new messages..."), callerUin));
#endif		
					XmlDocument doc = new XmlDocument();
					XmlNode historyNode;
					
					try
					{
						doc.Load(historyFileName);
					}
					catch(Exception e)
					{
#if LOG
						logger.ErrorException(String.Format(Catalog.GetString("Unable to load history file: {0}"), historyFileName), e);
						logger.Trace(String.Format(Catalog.GetString("Attempting to overwrite file: {0}"), historyFileName));
#endif	
						SerializeChatHistory(callerUin, historyFileName);
						
						chatsMutex.ReleaseMutex();
						return;
					}

					try
					{
						historyNode = doc.SelectSingleNode("/ArrayOfHistoryMsg");
					}
					catch(Exception e)
					{
#if LOG
						logger.ErrorException(String.Format(Catalog.GetString("Incorrect inner file organization: {0}"), historyFileName), e);
						logger.Trace(String.Format(Catalog.GetString("Attempting to overwrite file: {0}"), historyFileName));
#endif
						SerializeChatHistory(callerUin, historyFileName);
						
						chatsMutex.ReleaseMutex();
						return;
					}
					
					
					foreach (HistoryMsg hm in chats[callerUin])
					{
						XmlElement histMsgElement = doc.CreateElement("HistoryMsg");
						historyNode.AppendChild(histMsgElement);
						
						XmlElement uin = histMsgElement.OwnerDocument.CreateElement("U");
						uin.InnerText = hm.U.ToString();
						histMsgElement.AppendChild(uin);
						
						XmlElement name = histMsgElement.OwnerDocument.CreateElement("N");
						name.InnerText = hm.N;
						histMsgElement.AppendChild(name);
						
						XmlElement date = histMsgElement.OwnerDocument.CreateElement("D");
						date.InnerText = hm.D;
						histMsgElement.AppendChild(date);
						
						XmlElement message = histMsgElement.OwnerDocument.CreateElement("M");
						message.InnerText = hm.M;
						histMsgElement.AppendChild(message);
						
						XmlElement type = histMsgElement.OwnerDocument.CreateElement("T");
						type.InnerText = hm.T.ToString();
						histMsgElement.AppendChild(type);
					}	
					
					try
					{
						doc.Save(historyFileName);
#if LOG
						logger.Trace(String.Format(Catalog.GetString("Chat history saved succesfully succesfuly to: {0}"), historyFileName));
#endif 
					}
					catch (Exception e)
					{
#if LOG
						logger.ErrorException(String.Format(Catalog.GetString("Unable to append messages to file: {0}"), historyFileName), e);
						logger.Trace(String.Format(Catalog.GetString("Attempting to overwrite file: {0}"), historyFileName));
#endif
						SerializeChatHistory(callerUin, historyFileName);
						
						chatsMutex.ReleaseMutex();
						return;
					}
					
					// clear messages list
					chats[callerUin].Clear();
				}
			}
			chatsMutex.ReleaseMutex();
		}
		
		/// <summary>
		/// Save messages list using automatic serialization
		/// </summary>
		/// <remarks>
		/// This method is NOT thread-safe
		/// </remarks>
		private void SerializeChatHistory(int callerUin, string filePath)
		{
			try
			{
				fs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
				XmlSerializer ser = new XmlSerializer(typeof(List<HistoryMsg>));
				ser.Serialize(fs, chats[callerUin]);
#if LOG
				logger.Trace(Catalog.GetString("Chat history saved succesfully succesfuly"));
#endif 
				// clear messages list
				chats[callerUin].Clear();
			}
			catch (Exception e)
			{
#if LOG
				logger.ErrorException(String.Format(Catalog.GetString("Unable to save chat history to file: {0}"), filePath), e);
#endif 					
				dispatcher.MainWindow.HistoryErrorMessageDialog(
				                                                String.Format(
				                                                              Catalog.GetString("Unable to save chat history to file: {0}\n{1}"), filePath, e));
			}
			finally
			{
				if (fs != null)
					fs.Close();
				fs = null;
			}	
		}
		
		/// <summary>
		/// Loads information about available history files
		/// </summary>
		/// <returns>
		/// A <see cref="HistoryTreeItemData"/>
		/// array of <c>wx.TreeItemData</c> objects
		/// that can be used to represent history stored files in 
		/// <c>wx.TreeCtrl</c> widget on <c>HistoryFrame</c>
		/// </returns>
		public HistoryTreeItemData[] GetContactsHistory()
		{
			if (!Directory.Exists(dispatcher.AppCfg.HistoryDir))
			{
#if LOG
				logger.Trace(String.Format(Catalog.GetString("History directory: {0} don't exist, no history data found"), dispatcher.AppCfg.HistoryDir));
#endif		
				return new HistoryTreeItemData[0];
			}
			
			List<HistoryTreeItemData> dirList = new List<HistoryTreeItemData>();
			
			try
			{
				DirectoryInfo di = new DirectoryInfo(dispatcher.AppCfg.HistoryDir);
				DirectoryInfo[] dirs = di.GetDirectories();
								
				foreach(DirectoryInfo dirName in dirs)
				{
					int uin;
					if (Int32.TryParse(dirName.Name, out uin))
					{
						HistoryTreeItemData newDir = new HistoryTreeItemData();
						newDir.Uin = uin;
						if (dispatcher.ContactMgr.Contacts.ContainsKey(uin))
							newDir.Name = dispatcher.ContactMgr.Contacts[uin].Name;
						
						dirList.Add(newDir);
					}
				}
			}
			catch (Exception e)
			{
#if LOG
				logger.ErrorException(String.Format(Catalog.GetString("Unable to load content of: {0} directory"), dispatcher.AppCfg.HistoryDir), e);
#endif		
				return new HistoryTreeItemData[0];
			}
			
			// sort list alphabeticaly
			dirList.Sort();
			
			return dirList.ToArray();
		}
		
		
		/// <summary>
		/// Loads history file and returns formatted history content
		/// </summary>
		/// <param name="filePath">
		/// A <see cref="System.String"/>
		/// path to history file
		/// </param>
		/// <returns>
		/// A <see cref="System.String"/>
		/// formatted (HTML) content of history file
		/// </returns>
		public string GetChatHtmlCode(string filePath)
		{
			List<HistoryMsg> chatMessages;
			
			try
			{
				fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
				XmlSerializer ser = new XmlSerializer(typeof(List<HistoryMsg>));
				chatMessages = (List<HistoryMsg>) ser.Deserialize(fs);
			}
			catch (Exception e)
			{
#if LOG
				logger.ErrorException(String.Format(Catalog.GetString("Unable to load history file: {0}"), filePath), e);
#endif 					
				dispatcher.MainWindow.HistoryErrorMessageDialog(
				                                                String.Format(
				                                                              Catalog.GetString("Unable to load history file: {0}\n{1}"), filePath, e));
				return string.Empty;
			}
			finally
			{
				if (fs != null)
					fs.Close();
				fs = null;
			}	
			
			StringBuilder messageBuilder = new StringBuilder();
			string chatHtmlPattern = "<html>" +
				"<head>" +
				"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />" +
				"</head>" +
				"<body>"  +
					"<table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" width=\"100%\" height=\"100%\">" +
					"#{messages}" +
					"</table>" +
				"</body>" +
				"</html>";
			Regex messagesRegex = new Regex("#{messages}");		
			
			// format each message
			foreach (HistoryMsg msg in chatMessages)
			{
				if (msg.T == ChatMessageType.IN)
					messageBuilder.Append(inFormatter.FormatMessage(msg));
				else
					messageBuilder.Append(outFormatter.FormatMessage(msg));
			}
			
			// return complete html code
			return messagesRegex.Replace(chatHtmlPattern, messageBuilder.ToString(), 1);	
		}
	}
}
